/*-------------------------------------------------------------------------
 *
 * visibilitymap.c
 *	  跟踪堆元组可见性的位图
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/access/heap/visibilitymap.c
 *
 * 接口例程
 *		visibilitymap_clear  - 清除可见性图中一个页面的位
 *		visibilitymap_pin	 - 针对设置位而固定一个映射页面
 *		visibilitymap_pin_ok - 检查所需映射页面是否已固定
 *		visibilitymap_set	 - 在先前固定的页面上设置位
 *		visibilitymap_get_status - 获取位状态
 *		visibilitymap_count  - 计算可见性图中设置的位数
 *		visibilitymap_prepare_truncate -
 *			准备截断可见性图
 *
 * 注意
 *
 * 可见性图是一个位图，每个堆页面有两个位（全可见和全冻结）。
 * 设置全可见位表示页面上的所有元组对所有事务已知可见，
 * 因此该页面不需要被清理。设置全冻结位表示页面上的所有
 * 元组完全冻结，因此即使需要全表扫描清理（例如防止换页
 * 清理），该页面也不需要被清理。只有在页面已完全可见时，
 * 才能设置全冻结位。
 *
 * 该图是保守的，因为我们确保每当设置一位时，我们知道条件为真，
 * 但如果未设置，则可能为真也可能为假。
 *
 * 清除可见性图位不会单独 WAL 记录。调用者必须确保每当清除
 * 一位时，该位在 WAL 重放更新操作时也被清除。
 *
 * 当我们在 VACUUM 期间 *设置* 可见性图时，必须写入 WAL。
 * 这可能看起来与直觉相悖，因为该位基本上是一个提示：
 * 如果它是清除的，仍然可能每个元组都对所有事务可见；
 * 我们只是不能确定这一点。困难在于，通常有两个位同时设定：
 * 页面的 PD_ALL_VISIBLE 位和可见性图位。如果在可见性图页面
 * 被写入磁盘后发生崩溃，并且在更新的堆页面写入磁盘之前，
 * 重做则必须在堆页面上设置该位。否则，下一次在堆页面上
 * 插入、更新或删除将无法意识到可见性图位必须被清除，
 * 可能导致索引唯一扫描返回错误的答案。
 *
 * VACUUM 通常会跳过可见性图位已设置的页面；这样的页面不能
 * 包含任何已删除的元组，因此不需要清理。
 *
 * 锁定
 *
 * 在 heapam.c 中，每当修改一个页面以便页面上的所有元组
 * 不再对每个人可见时，相应的位在可见性图中被清除。为了
 * 确保崩溃安全，我们需要在仍持有对堆页面的锁并在记录
 * 页面修改的相同临界区内进行此操作。然而，我们不想在
 * 读取可见性图页面所需的任何 I/O 上长时间持有缓冲区锁。
 * 为了避免这种情况，我们在锁定页面之前检查堆页面；
 * 如果页面级 PD_ALL_VISIBLE 位已设置，我们锁定可见性图
 * 位。然后，我们锁定缓冲区。但这会产生竞争条件：
 * 在锁定缓冲区所需的时间内，PD_ALL_VISIBLE 位可能会被设置。
 * 如果发生这种情况，我们必须解锁缓冲区，锁定可见性图页面，
 * 然后再次锁定缓冲区。这种情况不应该经常发生，因为
 * 当前只有 VACUUM 设置可见性图位，竞争仅会在 VACUUM
 * 在几乎完全相同的时间处理给定页面时发生。
 *
 * 要设置一位，您需要持有对堆页面的锁。这防止了竞争条件，
 * 其中 VACUUM 看到页面上的所有元组对每个人都是可见的，
 * 但另一个后端在 VACUUM 设置可见性图位之前修改了页面。
 *
 * 当一位被设置时，更新可见性图页面的 LSN，以确保可见性图
 * 更新不会在允许设置该位的变化的 WAL 记录被刷新到磁盘之前写入磁盘。
 * 但当一位被清除时，我们不必这样做，因为从正确性角度看，
 * 清除映射中的一位始终是安全的。
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam_xlog.h"
#include "access/visibilitymap.h"
#include "access/xloginsert.h"
#include "access/xlogutils.h"
#include "miscadmin.h"
#include "port/pg_bitutils.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/smgr.h"
#include "utils/inval.h"


/*#define TRACE_VISIBILITYMAP */

/*
 * 每个可见性映射页面上位图的大小（以字节为单位）。没有
 * 额外的头，因此整个页面减去标准页面头用于
 * 位图。
 */
#define MAPSIZE (BLCKSZ - MAXALIGN(SizeOfPageHeaderData))

/* 我们可以在一个字节中表示的堆块数量 */
#define HEAPBLOCKS_PER_BYTE (BITS_PER_BYTE / BITS_PER_HEAPBLOCK)

/* 我们可以在一个可见性映射页面中表示的堆块数量。 */
#define HEAPBLOCKS_PER_PAGE (MAPSIZE * HEAPBLOCKS_PER_BYTE)

/* 从堆块号到可见性映射中正确位的映射 */
#define HEAPBLK_TO_MAPBLOCK(x) ((x) / HEAPBLOCKS_PER_PAGE)
#define HEAPBLK_TO_MAPBYTE(x) (((x) % HEAPBLOCKS_PER_PAGE) / HEAPBLOCKS_PER_BYTE)
#define HEAPBLK_TO_OFFSET(x) (((x) % HEAPBLOCKS_PER_BYTE) * BITS_PER_HEAPBLOCK)

/* 用于计数可见性映射中位子集的掩码。 */
#define VISIBLE_MASK64	UINT64CONST(0x5555555555555555) /* 每对
														 * 位对的低位 */
#define FROZEN_MASK64	UINT64CONST(0xaaaaaaaaaaaaaaaa) /* 每对
														 * 位对的高位 */

/* 内部例程的原型 */
static Buffer fc_vm_readbuf(Relation fc_rel, BlockNumber fc_blkno, bool fc_extend);
static void fc_vm_extend(Relation fc_rel, BlockNumber fc_vm_nblocks);


/*
 *	visibilitymap_clear - 清除可见性映射中一个页面的指定位
 *
 * 您必须传递一个包含正确映射页面的缓冲区给此函数。
 * 首先调用 visibilitymap_pin 来固定正确的一个。此函数不进行
 * 任何 I/O。如果有任何位被清除，则返回 true，否则返回 false。
 */
bool visibilitymap_clear(Relation fc_rel, BlockNumber fc_heapBlk, Buffer fc_buf, uint8 fc_flags)
{
	BlockNumber fc_mapBlock = HEAPBLK_TO_MAPBLOCK(fc_heapBlk);
	int			fc_mapByte = HEAPBLK_TO_MAPBYTE(fc_heapBlk);
	int			fc_mapOffset = HEAPBLK_TO_OFFSET(fc_heapBlk);
	uint8		fc_mask = fc_flags << fc_mapOffset;
	char	   *fc_map;
	bool		fc_cleared = false;

	Assert(fc_flags & VISIBILITYMAP_VALID_BITS);

#ifdef TRACE_VISIBILITYMAP
	elog(DEBUG1, "vm_clear %s %d", RelationGetRelationName(fc_rel), fc_heapBlk);
#endif

	if (!BufferIsValid(fc_buf) || BufferGetBlockNumber(fc_buf) != fc_mapBlock)
		elog(ERROR, "wrong buffer passed to visibilitymap_clear");

	LockBuffer(fc_buf, BUFFER_LOCK_EXCLUSIVE);
	fc_map = PageGetContents(BufferGetPage(fc_buf));

	if (fc_map[fc_mapByte] & fc_mask)
	{
		fc_map[fc_mapByte] &= ~fc_mask;

		MarkBufferDirty(fc_buf);
		fc_cleared = true;
	}

	LockBuffer(fc_buf, BUFFER_LOCK_UNLOCK);

	return fc_cleared;
}

/*
 *	visibilitymap_pin - 固定一个映射页面以设置一个位
 *
 * 在可见性映射中设置一个位是一个两阶段操作。首先，调用
 * visibilitymap_pin，固定包含堆页面位的可见性映射页面。
 * 因为这可能需要 I/O 来读取映射页面，所以您
 * 在执行此操作时不应该持有堆页面上的锁。然后，调用
 * visibilitymap_set 实际设置位。
 *
 * 进入时，*buf 应该是 InvalidBuffer 或者是通过
 * 对同一关系的早期调用 visibilitymap_pin 或 visibilitymap_get_status 返回的有效缓冲区。返回时，*buf 是一个有效的缓冲区，其中包含
 * heapBlk 的位的映射页面。
 *
 * 如果页面在映射文件中还不存在，则会扩展它。
 */
void visibilitymap_pin(Relation fc_rel, BlockNumber fc_heapBlk, Buffer *fc_buf)
{
	BlockNumber fc_mapBlock = HEAPBLK_TO_MAPBLOCK(fc_heapBlk);

	/* 如果可能，重用旧的固定缓冲区 */
	if (BufferIsValid(*fc_buf))
	{
		if (BufferGetBlockNumber(*fc_buf) == fc_mapBlock)
			return;

		ReleaseBuffer(*fc_buf);
	}
	*fc_buf = fc_vm_readbuf(fc_rel, fc_mapBlock, true);
}

/*
 *	visibilitymap_pin_ok - 我们是否已经固定了正确的页面？
 *
 * 在进入时，buf 应该是 InvalidBuffer 或者是通过
 * 对同一关系的早期调用 visibilitymap_pin 或 visibilitymap_get_status 返回的有效缓冲区。返回值指示缓冲区是否覆盖给定的 heapBlk。
 */
bool visibilitymap_pin_ok(BlockNumber fc_heapBlk, Buffer fc_buf)
{
	BlockNumber fc_mapBlock = HEAPBLK_TO_MAPBLOCK(fc_heapBlk);

	return BufferIsValid(fc_buf) && BufferGetBlockNumber(fc_buf) == fc_mapBlock;
}

/*
 *	visibilitymap_set - 在先前固定的页面上设置位
 *
 * recptr 是我们正在重放的 XLOG 记录的 LSN，如果我们处于恢复中，或者在正常运行中为 InvalidXLogRecPtr。页面 LSN 被推进到提供的值；在正常运行中，我们生成一个新的 XLOG 记录，并将页面 LSN 设置为该值。cutoff_xid 是被标记为完全可见页面上最大的 xmin；它是 Hot Standby 所必需的，如果页面不包含元组，则可以为 InvalidTransactionId。当一个已经全可见的页面被标记为全冻结时，也可以设置为 InvalidTransactionId。
 *
 * 调用者应在调用此函数之前设置堆页面的 PD_ALL_VISIBLE 位。除恢复外，调用者还应传递堆缓冲区。当启用校验和且我们不在恢复中时，必须将堆缓冲区添加到 WAL 链中，以保护其不被撕裂。
 *
 * 你必须向此函数传递一个包含正确映射页面的缓冲区。首先调用 visibilitymap_pin 钉住正确的页面。此函数不进行任何 I/O。
 */
void visibilitymap_set(Relation fc_rel, BlockNumber fc_heapBlk, Buffer fc_heapBuf,
				  XLogRecPtr fc_recptr, Buffer fc_vmBuf, TransactionId fc_cutoff_xid,
				  uint8 fc_flags)
{
	BlockNumber fc_mapBlock = HEAPBLK_TO_MAPBLOCK(fc_heapBlk);
	uint32		fc_mapByte = HEAPBLK_TO_MAPBYTE(fc_heapBlk);
	uint8		fc_mapOffset = HEAPBLK_TO_OFFSET(fc_heapBlk);
	Page		fc_page;
	uint8	   *fc_map;

#ifdef TRACE_VISIBILITYMAP
	elog(DEBUG1, "vm_set %s %d", RelationGetRelationName(fc_rel), fc_heapBlk);
#endif

	Assert(InRecovery || XLogRecPtrIsInvalid(fc_recptr));
	Assert(InRecovery || BufferIsValid(fc_heapBuf));
	Assert(fc_flags & VISIBILITYMAP_VALID_BITS);

	/* 检查我们是否固定了正确的堆页面（如果存在） */
	if (BufferIsValid(fc_heapBuf) && BufferGetBlockNumber(fc_heapBuf) != fc_heapBlk)
		elog(ERROR, "wrong heap buffer passed to visibilitymap_set");

	/* 检查我们是否固定了正确的 VM 页面 */
	if (!BufferIsValid(fc_vmBuf) || BufferGetBlockNumber(fc_vmBuf) != fc_mapBlock)
		elog(ERROR, "wrong VM buffer passed to visibilitymap_set");

	fc_page = BufferGetPage(fc_vmBuf);
	fc_map = (uint8 *) PageGetContents(fc_page);
	LockBuffer(fc_vmBuf, BUFFER_LOCK_EXCLUSIVE);

	if (fc_flags != (fc_map[fc_mapByte] >> fc_mapOffset & VISIBILITYMAP_VALID_BITS))
	{
		START_CRIT_SECTION();

		fc_map[fc_mapByte] |= (fc_flags << fc_mapOffset);
		MarkBufferDirty(fc_vmBuf);

		if (RelationNeedsWAL(fc_rel))
		{
			if (XLogRecPtrIsInvalid(fc_recptr))
			{
				Assert(!InRecovery);
				fc_recptr = log_heap_visible(fc_rel->rd_node, fc_heapBuf, fc_vmBuf,
										  fc_cutoff_xid, fc_flags);

				/*
				 * 如果启用了数据校验和（或 wal_log_hints=on），我们
				 * 需要保护堆页面不被撕裂。
				 */
				if (XLogHintBitIsNeeded())
				{
					Page		fc_heapPage = BufferGetPage(fc_heapBuf);

					/* 调用者应首先设置 PD_ALL_VISIBLE */
					Assert(PageIsAllVisible(fc_heapPage));
					PageSetLSN(fc_heapPage, fc_recptr);
				}
			}
			PageSetLSN(fc_page, fc_recptr);
		}

		END_CRIT_SECTION();
	}

	LockBuffer(fc_vmBuf, BUFFER_LOCK_UNLOCK);
}

/*
 *	visibilitymap_get_status - 获取位的状态
 *
 * 根据可见性映射，堆块上的所有元组是否对所有可见或被标记为冻结？
 *
 * 进入时，*buf 应为 InvalidBuffer 或以前对同一关系调用 visibilitymap_pin 或 visibilitymap_get_status 返回的有效缓冲区。返回时，*buf 是一个有效的缓冲区，包含 heapBlk 的位的映射页面，或者是 InvalidBuffer。调用者有责任在完成测试和设置位后释放 *buf。
 *
 * 注意：此函数通常在没有锁定堆页面的情况下调用，因此在我们查看它之后，其他人可能会更改该位。实际上，由于我们也不锁定可见性映射页面，因此有人在我们查看它之前已经改变了该位，但我们可能仍会看到旧值。处理所有并发问题是调用者的责任！
 */
uint8 visibilitymap_get_status(Relation fc_rel, BlockNumber fc_heapBlk, Buffer *fc_buf)
{
	BlockNumber fc_mapBlock = HEAPBLK_TO_MAPBLOCK(fc_heapBlk);
	uint32		fc_mapByte = HEAPBLK_TO_MAPBYTE(fc_heapBlk);
	uint8		fc_mapOffset = HEAPBLK_TO_OFFSET(fc_heapBlk);
	char	   *fc_map;
	uint8		fc_result;

#ifdef TRACE_VISIBILITYMAP
	elog(DEBUG1, "vm_get_status %s %d", RelationGetRelationName(fc_rel), fc_heapBlk);
#endif

	/* 如果可能，重用旧的固定缓冲区 */
	if (BufferIsValid(*fc_buf))
	{
		if (BufferGetBlockNumber(*fc_buf) != fc_mapBlock)
		{
			ReleaseBuffer(*fc_buf);
			*fc_buf = InvalidBuffer;
		}
	}

	if (!BufferIsValid(*fc_buf))
	{
		*fc_buf = fc_vm_readbuf(fc_rel, fc_mapBlock, false);
		if (!BufferIsValid(*fc_buf))
			return false;
	}

	fc_map = PageGetContents(BufferGetPage(*fc_buf));

	/*
	 * 单字节读取是原子的。这里可能会存在内存排序效应，
	 * 但出于性能原因，我们将其视为调用者的职责。
	 */
	fc_result = ((fc_map[fc_mapByte] >> fc_mapOffset) & VISIBILITYMAP_VALID_BITS);
	return fc_result;
}

/*
 *	visibilitymap_count  - 统计可见性映射中设置位的数量
 *
 * 注意：我们忽略在表被并发扩展时可能出现的竞争条件。添加到表的新页面不会被标记为全可见或全冻结，因此它们不会影响结果。
 */
void visibilitymap_count(Relation fc_rel, BlockNumber *fc_all_visible, BlockNumber *fc_all_frozen)
{
	BlockNumber fc_mapBlock;
	BlockNumber fc_nvisible = 0;
	BlockNumber fc_nfrozen = 0;

	/* 必须指定 all_visible */
	Assert(fc_all_visible);

	for (fc_mapBlock = 0;; fc_mapBlock++)
	{
		Buffer		fc_mapBuffer;
		uint64	   *fc_map;
		int			fc_i;

		/*
		 * 一直读到地图的末尾。我们假设最后一页中的任何多余字节都是零，所以我们不在计数中排除它们。
		 */
		fc_mapBuffer = fc_vm_readbuf(fc_rel, fc_mapBlock, false);
		if (!BufferIsValid(fc_mapBuffer))
			break;

		/*
		 * 我们选择不锁定页面，因为如果有人同时设置或清除位，结果无论如何都会立即过时，而我们只需要一个大致的值。
		 */
		fc_map = (uint64 *) PageGetContents(BufferGetPage(fc_mapBuffer));

		StaticAssertStmt(MAPSIZE % sizeof(uint64) == 0,
						 "unsupported MAPSIZE");
		if (fc_all_frozen == NULL)
		{
			for (fc_i = 0; fc_i < MAPSIZE / sizeof(uint64); fc_i++)
				fc_nvisible += pg_popcount64(fc_map[fc_i] & VISIBLE_MASK64);
		}
		else
		{
			for (fc_i = 0; fc_i < MAPSIZE / sizeof(uint64); fc_i++)
			{
				fc_nvisible += pg_popcount64(fc_map[fc_i] & VISIBLE_MASK64);
				fc_nfrozen += pg_popcount64(fc_map[fc_i] & FROZEN_MASK64);
			}
		}

		ReleaseBuffer(fc_mapBuffer);
	}

	*fc_all_visible = fc_nvisible;
	if (fc_all_frozen)
		*fc_all_frozen = fc_nfrozen;
}

/*
 *	visibilitymap_prepare_truncate -
 *			准备截断可见性地图
 *
 * nheapblocks 是堆的新大小。
 *
 * 返回新的可见性地图块的数量。
 * 如果是 InvalidBlockNumber，则没有东西可以截断；
 * 否则，调用者负责调用 smgrtruncate() 来截断可见性地图页面。
 */
BlockNumber visibilitymap_prepare_truncate(Relation fc_rel, BlockNumber fc_nheapblocks)
{
	BlockNumber fc_newnblocks;

	/* 最后剩余的块、字节和位 */
	BlockNumber fc_truncBlock = HEAPBLK_TO_MAPBLOCK(fc_nheapblocks);
	uint32		fc_truncByte = HEAPBLK_TO_MAPBYTE(fc_nheapblocks);
	uint8		fc_truncOffset = HEAPBLK_TO_OFFSET(fc_nheapblocks);

#ifdef TRACE_VISIBILITYMAP
	elog(DEBUG1, "vm_truncate %s %d", RelationGetRelationName(fc_rel), fc_nheapblocks);
#endif

	/*
	 * 如果尚未为该关系创建可见性地图，则没有东西可以截断。
	 */
	if (!smgrexists(RelationGetSmgr(fc_rel), VISIBILITYMAP_FORKNUM))
		return InvalidBlockNumber;

	/*
	 * 除非新大小恰好位于可见性地图页面边界上，否则最后剩余的地图页面中的尾部位需要被清除，表示被截断的堆块。这不仅整洁，而且是必要的，因为如果堆再次扩展，我们没有机会清除这些位。
	 */
	if (fc_truncByte != 0 || fc_truncOffset != 0)
	{
		Buffer		fc_mapBuffer;
		Page		fc_page;
		char	   *fc_map;

		fc_newnblocks = fc_truncBlock + 1;

		fc_mapBuffer = fc_vm_readbuf(fc_rel, fc_truncBlock, false);
		if (!BufferIsValid(fc_mapBuffer))
		{
			/* 没有要做的，文件已经比请求的大小小 */
			return InvalidBlockNumber;
		}

		fc_page = BufferGetPage(fc_mapBuffer);
		fc_map = PageGetContents(fc_page);

		LockBuffer(fc_mapBuffer, BUFFER_LOCK_EXCLUSIVE);

		/* 从这里到更改记录时不报告错误 */
		START_CRIT_SECTION();

		/* 清除不必要的字节。 */
		MemSet(&fc_map[fc_truncByte + 1], 0, MAPSIZE - (fc_truncByte + 1));

		/*----
		 * 通过掩码清除最后一个剩余字节的不必要位。
		 *
		 * ((1 << 0) - 1) = 00000000
		 * ((1 << 1) - 1) = 00000001
		 * ...
		 * ((1 << 6) - 1) = 00111111
		 * ((1 << 7) - 1) = 01111111
		 *----
		 */
		fc_map[fc_truncByte] &= (1 << fc_truncOffset) - 1;

		/*
		 * 截断关系在更高层次上会被 WAL 记录，我们将在 WAL 回放时被调用。但是如果启用了校验和，我们仍然需要写入 WAL 记录以防止撕裂页，如果页面在截断 WAL 记录之前写入磁盘。我们不能在这里使用 MarkBufferDirtyHint，因为这在恢复期间不会弄脏页面。
		 */
		MarkBufferDirty(fc_mapBuffer);
		if (!InRecovery && RelationNeedsWAL(fc_rel) && XLogHintBitIsNeeded())
			log_newpage_buffer(fc_mapBuffer, false);

		END_CRIT_SECTION();

		UnlockReleaseBuffer(fc_mapBuffer);
	}
	else
		fc_newnblocks = fc_truncBlock;

	if (smgrnblocks(RelationGetSmgr(fc_rel), VISIBILITYMAP_FORKNUM) <= fc_newnblocks)
	{
		/* 没有要做的，文件已经比请求的大小小 */
		return InvalidBlockNumber;
	}

	return fc_newnblocks;
}

/*
 * 读取可见性地图页面。
 *
 * 如果页面不存在，则返回 InvalidBuffer，或者如果“extend”为真，则扩展可见性地图文件。
 */
static Buffer fc_vm_readbuf(Relation fc_rel, BlockNumber fc_blkno, bool fc_extend)
{
	Buffer		fc_buf;
	SMgrRelation fc_reln;

	/*
	 * 注意：如果 relcache 条目被关闭，重用此 smgr 指针可能会失败。只要我们在这里和最后一次使用指针之间只执行 smgr 级别的操作，这样做是安全的。
	 */
	fc_reln = RelationGetSmgr(fc_rel);

	/*
	 * 如果我们尚未缓存可见性地图分支的大小，请先检查它。
	 */
	if (fc_reln->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] == InvalidBlockNumber)
	{
		if (smgrexists(fc_reln, VISIBILITYMAP_FORKNUM))
			smgrnblocks(fc_reln, VISIBILITYMAP_FORKNUM);
		else
			fc_reln->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] = 0;
	}

	/* 处理超出 EOF 的请求 */
	if (fc_blkno >= fc_reln->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM])
	{
		if (fc_extend)
			fc_vm_extend(fc_rel, fc_blkno + 1);
		else
			return InvalidBuffer;
	}

	/*
	 * 使用 ZERO_ON_ERROR 模式，并在必要时初始化页面。清除位始终是安全的，因此清除损坏的页面比出错更好。
	 *
	 * 初始化页面的部分比看起来复杂，因为可能会有多个后端同时进行此操作，并且我们希望在页面正常的情况下，不必无谓地获取缓冲锁。我们必须获取锁以初始化页面，因此在获得锁后重新检查页面的新鲜度，以防其他人已经完成了此操作。另外，因为我们最初在没有锁的情况下检查 PageIsNew，所以可能会发生在其他人仍在初始化页面时返回缓冲（即，我们可能会看到 pd_upper 被设置，但其他页面头字段仍为零）。对于自己将获取缓冲锁的调用者来说，这是无害的，但有些调用者在没有任何锁的情况下检查页面。后者只有在不依赖于页面头具有正确内容的情况下才是可以的。当前的使用是安全的，因为 PageGetContents() 不要求这样。
	 */
	fc_buf = ReadBufferExtended(fc_rel, VISIBILITYMAP_FORKNUM, fc_blkno,
							 RBM_ZERO_ON_ERROR, NULL);
	if (PageIsNew(BufferGetPage(fc_buf)))
	{
		LockBuffer(fc_buf, BUFFER_LOCK_EXCLUSIVE);
		if (PageIsNew(BufferGetPage(fc_buf)))
			PageInit(BufferGetPage(fc_buf), BLCKSZ, 0);
		LockBuffer(fc_buf, BUFFER_LOCK_UNLOCK);
	}
	return fc_buf;
}

/*
 * 确保可见性映射分叉至少是 vm_nblocks 长，如果必要，使用零页扩展它。
 */
static void fc_vm_extend(Relation fc_rel, BlockNumber fc_vm_nblocks)
{
	BlockNumber fc_vm_nblocks_now;
	PGAlignedBlock fc_pg;
	SMgrRelation fc_reln;

	PageInit((Page) fc_pg.data, BLCKSZ, 0);

	/*
	 * 我们使用关系扩展锁来锁定其他后端同时尝试扩展可见性映射。它还不必要地锁定主分叉的扩展，但扩展可见性映射的情况很少，因此似乎没有必要为此设置一个单独的锁类型。
	 *
	 * 注意，另一后端在我们获取锁之前可能已经扩展或创建了关系。
	 */
	LockRelationForExtension(fc_rel, ExclusiveLock);

	/*
	 * 注意：如果 relcache 条目被关闭，重用此 smgr 指针可能会失败。只要我们在这里和最后一次使用指针之间只执行 smgr 级别的操作，这样做是安全的。
	 */
	fc_reln = RelationGetSmgr(fc_rel);

	/*
	 * 如果文件不存在，则首先创建文件。如果 smgr_vm_nblocks 为正，则它必须存在，无需进行 smgrexists 调用。
	 */
	if ((fc_reln->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] == 0 ||
		 fc_reln->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] == InvalidBlockNumber) &&
		!smgrexists(fc_reln, VISIBILITYMAP_FORKNUM))
		smgrcreate(fc_reln, VISIBILITYMAP_FORKNUM, false);

	/* 使缓存无效，以便 smgrnblocks() 请求内核。 */
	fc_reln->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] = InvalidBlockNumber;
	fc_vm_nblocks_now = smgrnblocks(fc_reln, VISIBILITYMAP_FORKNUM);

	/* 现在扩展文件 */
	while (fc_vm_nblocks_now < fc_vm_nblocks)
	{
		PageSetChecksumInplace((Page) fc_pg.data, fc_vm_nblocks_now);

		smgrextend(fc_reln, VISIBILITYMAP_FORKNUM, fc_vm_nblocks_now, fc_pg.data, false);
		fc_vm_nblocks_now++;
	}

	/*
	 * 发送一个共享无效消息，强制其他后端关闭它们可能对这个 rel 的任何 smgr 引用，我们即将对此进行修改。这是一个有用的优化，因为这意味着后端不必不断检查文件的创建或扩展，这种情况发生得很少。
	 */
	CacheInvalidateSmgr(fc_reln->smgr_rnode);

	UnlockRelationForExtension(fc_rel, ExclusiveLock);
}
